Repository navigation
docs: error-handling, error-catalog and cli pages teach the optional controlled_by_parent master - #31164
Conversation
…controlled_by_parent master A controlled_by_parent object may declare its only master_detail with required: false (ADR-0055 §1 as amended). A stored empty reference on it is not a 422: the row is its owner's, read by the owner and View All on the detail and written by the owner and Modify All. The 422 MISSING_REQUIRED_FIELD exception now names a required master only; the declaration table gains the optional-master row; the v18 refused-shape list no longer lists an explicit required: false; and --parent-write-reach names the null-master records it proves. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ihL2ebbZkr2QAEG1x7UC8
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ihL2ebbZkr2QAEG1x7UC8
|
domain:devx#2 · marchtian · session_017ihL2ebbZkr2QAEG1x7UC8 Contract reviewServed-tier:
① Derived judgmentsTruth of each sentence the diff adds or changes, against the code:
Consistency: the wording agrees with the merged permissions pages — ② Semver levelDocs only: three ③ Boundary flags
④ Design judgmentNo
Neither changes an access answer the page gives a holder of the right grants; neither is a must-fix. Round 2 delta — head
|
…the optional-master table cell agree with its paragraph A principal who cannot read a null-master row meets the by-id read door first and is answered 404 RECORD_NOT_FOUND, as for every hidden row; the 403 is for everyone else who reaches the gate. On the declaration table the optional-master row now says an insert that leaves the reference out is not refused, while an update that empties it is judged by the master it leaves (403 PERMISSION_DENIED without edit access); the catalog's prose says the same. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ihL2ebbZkr2QAEG1x7UC8
|
domain:devx#2 · marchtian · session_017ihL2ebbZkr2QAEG1x7UC8 维护者速读
席位意见:可以落地。复审两轮,第 1 轮的两项应改已在第 2 轮修正并通过增量复审,检查全绿。顺带发现 spec 迁移说明里的同一个过时说法,已立卡 #31169,交给 spec 车道。 席位核对 — ACCEPT ·
|
Requested by 田騏 · project thread
Fixes #31095
Clause-②: no
Metadata-protocol: none
维护者速读
controlled_by_parent子对象的主表引用存空一律 422」。现在写明:只有必填主表(required: true)的存储空引用才回422 MISSING_REQUIRED_FIELD;可选主表(唯一的master_detail写required: false)的空引用不是错误,这行归属主——属主和子对象「查看全部」可读,属主和「修改全部」可改删,其他人 403。CLI 页的 v18 升级清单不再把显式required: false列为被拒形态,os verify --parent-write-reach段补上它对「不挂主表的行」的证明。content/docs页面,不发布任何包,无运行时影响;回滚即 revert。content/docs/protocol/kernel/error-handling.mdx的MISSING_REQUIRED_FIELD小节(新增「可选主表」段和声明表新行),其次content/docs/api/error-catalog.mdx同名小节。pnpm check:doc-anchors && pnpm check:doc-authoring,预期两者 exit 0;再打开 error-handling 页的MISSING_REQUIRED_FIELD小节,应看到声明表里required: false一行写着「not refused」。What changes
Three
content/docspages taught acontrolled_by_parentmaster that is always required. Each site was re-read against the code onmainand reworded; the wording follows the permissions pages (content/docs/permissions/authorization.mdx,sharing-rules.mdx,rls.mdx,permissions-matrix.mdx): "its onlymaster_detaildeclaredrequired: false, an optional master (ADR-0055 §1 as amended)", "read by its owner and by View All", "written by its owner and by Modify All".protocol/kernel/error-handling.mdxstatus table (422 row)security-plugin-cbp-write-gate.ts: themasterId == null && rel.optionalblock returns beforeMasterReferenceMissingError;controlledByParentPredicateWriteMastersthrows it only when!rel.optionalMISSING_REQUIRED_FIELDexceptionrequired: true(flagged or not); new paragraph: an optional master's empty row is its owner's, read by owner + View All, updated/deleted by owner + Modify All; everyone else who reaches the gate403 PERMISSION_DENIED, a principal who cannot read the row404 RECORD_NOT_FOUNDfirst (round 2, S1; error-catalog says the same)nullMasterRowWritable+nullMasterOwnerDenied(PermissionDeniedError); the read door's 404 for a hidden addressed row:security-plugin-authz-middleware.tsrecordNotFoundErroraftercallerCannotReadAddressedRow; read filtercomputeControlledByParentFilternullMasterArm(security-plugin-cbp-read-filter.ts)required; "Only the first row is a declaration the parse accepts"required: false(optional master): an insert that leaves it out is not refused, an update that empties it is judged by the master it leaves (403 PERMISSION_DENIEDwithout edit access) (round 2, S2; error-catalog prose says the same); flagged rows read "required or optional"; "Only the first two rows"; insert naming no master admitted, emptying asks edit on the master it leaves,ownerMayEditdoes not cover itif (operation === 'insert') return;,askBodyNamedMaster(rel.optionalreturn), theownerMayEditleg's!(rel.optional && bodyEmptiesMasterReference(...));rel.masterNotWritablerefusal ahead of the optional block;controlledByParentMasterRefusaljudgesmaster-not-writableon the optional master too (controlled-by-parent-master.ts)master_detaildeclaredrequired: true; amaster_detailwithoutrequiredderives nonemaster_detaildeclaredrequired: false; no master: twomaster_detailnone required, two required, requiredlookuponly; an omittedrequiredis forced totruebyObjectSchema.create()and reported byos lintaterror, while a raw parse reads it asfalsecontrolledByParentMasterOf(controlled-by-parent-master.ts);object.zod.tsrefuseControlledByParentWithoutOneMasterdocblock (the amendment note on the omittedrequired) andforceCbpMasterDetailRequiredfk IN (readable masters)can never matchif (!rel.optional) return masterArm;api/error-catalog.mdxMISSING_REQUIRED_FIELDFix line, callout, "no master" paragraph, "not relaxed" paragraphdeployment/cli.mdxv18 "Not rewritten" tablerequired: false" as a refused stored shapecbp-optional-master-admittedand not listedrefused-shapes.tscontrolledByParentWithoutOneMaster(judges withcontrolledByParentMasterRefusal);migrations/entries/semantic/18.cbp-optional-master-admitted.ts--parent-write-reachparagraphmaster_detailfield names its master"packages/verify/src/parent-write-reach.tsmodule header andnullMasterReach;deriveParentWriteReachCasesmarksoptionalMasterfromcontrolledByParentMasterOfNo
content/docs/releases/**page is touched.Tests
Docs only: no code, no test. The derived docs gates below are the verification.
Acceptance notes
skip-changeset: the diff touches onlycontent/docs/**, which no package'sfiles[]ships.controlled_by_parentparenthetical does not name themaster-not-writableshape (areadonly/systemmaster), whichrefused-shapes.tslists too; the row's "an object registers but derives no master" is not true of that shape (it keeps its master and loses the save door). The 422 exception names "an update or delete by id"; a predicate write that selects such a row answers the same 422 (controlledByParentPredicateWriteMasters).Verification
Gate run on head
ba96749d36(round 2; round 1 ran the same 45 onf28b6db045, after mergingorigin/main); every commanddispatch-gates --commandsderives for this diff, exit codes captured before any pipe.Derived gates: 45 commands, all exit 0
--ranreconciliation:45 derived, 45 run, 0 NOT-MEASURED, 0 UNRUN— all 45 recorded an exit code, none of them 3 (dispatch-gates --ran, exit 0). Five gates first refused with exit 3 for a missing build (@objectstack/lint,@objectstack/specschema,@objectstack/client-reactdeclarations); the closures were built and every gate was rerun; round 2 reran all 45 onba96749d36, all exit 0.🤖 Generated with Claude Code